using System;
using System.Web;
using System.Web.Security;
using CKS.EBE.API;
using Microsoft.SharePoint;
using System.Diagnostics;
using System.Web.UI;
using System.Threading;
using Microsoft.SharePoint.Utilities;

namespace CKS.EBE
{
	public class BlogHttpModule : IHttpModule
	{
		private HttpApplication _HttpApplication;

		#region IHttpModule Members
		public void Dispose()
		{
		}

		public void Init(HttpApplication context)
		{
			_HttpApplication = context;
			_HttpApplication.PreRequestHandlerExecute += AppPreRequestHandlerExecute;
			_HttpApplication.BeginRequest += App_BeginRequest;
			_HttpApplication.PostAuthorizeRequest += AppPostAuthorizeRequest;
			_HttpApplication.PostReleaseRequestState += AppPostReleaseRequestState;
		}

		private static bool FileTypeHandled(Uri u)
		{
			string sPath = u.AbsolutePath.ToLower();

			// Exclude these paths from processing...causes an error when activating the publishing feature
			if (sPath.Contains("/_layouts/") || sPath.Contains("/_vti_bin/") || sPath.Contains("/_wpresources/")) return false;

			if (sPath.EndsWith("/")) sPath = sPath.Substring(0, sPath.Length - 1);

			string sExcludedPaths = System.Configuration.ConfigurationManager.AppSettings["EBE_ExcludedPaths"];
			if (!string.IsNullOrEmpty(sExcludedPaths))
			{
				string[] arPaths = sExcludedPaths.ToLower().Split(';');
				foreach (string path in arPaths)
				{
					if (sPath.Contains(path)) return false;
				}
			}


			if (sPath.EndsWith(".aspx") || sPath.EndsWith(".ashx"))
				return true;

			return false;
		}

		void AppPostAuthorizeRequest(object sender, EventArgs e)
		{
			try
			{
				HttpRequest httpRequest = HttpContext.Current.Request;
				if (!FileTypeHandled(httpRequest.Url)) return;
				// if we just deleted the web, the object still exists, but web.Exists is false
				if (!SPContext.Current.Web.Exists) return;
				BlogContext blogContext = BlogContext.Current;
				if (!blogContext.BlogEnabledSite) return;

				SPWeb web = SPContext.Current.Web;
				// blogListUrl is the name of the posts list
				string blogListUrl = Localization.GetResource("blogpost_Folder", "core", web.Language).ToLower();
				var request = httpRequest;
				string requestURL = request.Url.ToString().ToLower();
				string exportParam = httpRequest.Params["Export"];

				if (blogContext.Settings.RedirectPostAspx && requestURL.Contains(blogListUrl) &&
					 (requestURL.Contains("/post.aspx") || requestURL.Contains("/viewpost.aspx")) && blogContext.Settings.MtfEnabled
					 || exportParam != null)
				{
					Trace.WriteLine("Redirecting post.aspx?");

					SPList oPosts = Helper.GetListByFolderName(web, Localization.GetListName(Localization.BlogList.Posts, web.Language));
					if (request["ID"] == null) throw new ArgumentException("The post ID is null");
					int id = int.Parse(request["ID"]);

					SPListItem oPost = oPosts.GetItemById(id);

					if (oPost != null)
					{
						if (exportParam != null && string.Equals(exportParam, "PDF", StringComparison.OrdinalIgnoreCase))
						{
							// export the post to PDF
							var pdf = new PDF(oPost, web);
							pdf.DownloadFile(HttpContext.Current);
						}
						else
						{
							string sLink = blogContext.UrlProvider.createPostUrl(((DateTime)oPost["Created"]).ToString("yyyy-MM-dd HH:mm"), (string)oPost["BlogTitleForUrl"], null);
							HttpContext.Current.Response.Redirect("~" + sLink);
							return;
						}
					}
					Trace.WriteLine("Post wasn't found: " + request["ID"]);
				}

				// add pingback url to response header
				HttpContext.Current.Response.AddHeader("X-Pingback", string.Format("{0}/_layouts/pingback.axd", web.Url));

				string sRelPath = web.ServerRelativeUrl;
				if (!sRelPath.EndsWith("/")) sRelPath += "/";

				CheckAndThrowIfSystemPage(httpRequest.Url.AbsolutePath.ToLower(), sRelPath, blogContext);
			}
			catch (ThreadAbortException)
			{
				// will be thrown if redirected
			}
			catch (Exception ex)
			{
				Trace.Write(ex);
			}
		}

		/// <summary>
		/// system pages within the blog will be protected
		/// </summary>
		/// <param name="absolutePath">/blog/something.</param>
		/// <param name="sRelPath">/blog/</param>
		/// <param name="blogContext"></param>
		protected virtual void CheckAndThrowIfSystemPage(string absolutePath, string sRelPath, BlogContext blogContext)
		{
			if (blogContext.Settings.SecureSystemPages && absolutePath.Contains(sRelPath) &&
				 !absolutePath.EndsWith(FormsAuthentication.LoginUrl, StringComparison.OrdinalIgnoreCase) &&
				 !absolutePath.EndsWith("/_layouts/authenticate.aspx", StringComparison.OrdinalIgnoreCase))
			{
				const string errorMessage = "CKS Blog preventing access to system pages\r\n\r\n{0}";
				const string loginText = "Login";
				string loginLink = SPContext.Current.Web.Url + "/_layouts/Authenticate.aspx?Source=" + SPEncode.UrlEncode(absolutePath);

				if (absolutePath.Contains(sRelPath + "_layouts/") && !absolutePath.Contains(sRelPath + "_layouts/cks/ebe/") && absolutePath.Contains(".aspx") && !blogContext.IsBlogOwner && !blogContext.IsBlogContributor && !absolutePath.EndsWith("/captcha.aspx", StringComparison.OrdinalIgnoreCase) && !absolutePath.EndsWith("/signout.aspx", StringComparison.OrdinalIgnoreCase) && !absolutePath.Contains("/trackback.aspx"))
					SPUtility.TransferToErrorPage(errorMessage, loginText, loginLink);

				if (absolutePath.Contains(sRelPath + "forms/") && absolutePath.Contains(".aspx") && !blogContext.IsBlogOwner && !blogContext.IsBlogContributor)
					SPUtility.TransferToErrorPage(errorMessage, loginText, loginLink);

				if (absolutePath.Contains(sRelPath + "lists/") && absolutePath.Contains(".aspx") && !blogContext.IsBlogOwner && !absolutePath.Contains("viewpost") && !blogContext.IsBlogContributor)
				{
					SPUtility.TransferToErrorPage(errorMessage, loginText, loginLink);
				}
			}
		}

		void AppPreRequestHandlerExecute(object sender, EventArgs e)
		{
			if (!FileTypeHandled(HttpContext.Current.Request.Url)) return;

			BlogContext blogContext = BlogContext.Current;
			if (!blogContext.BlogEnabledSite) return;

			try
			{
				if (HttpContext.Current.Handler is Page)
				{
					var page = _HttpApplication.Context.CurrentHandler as Page;

					if (page != null)
					{
						page.PreInit += BlogHttpModule_PreInit;
						page.PreRenderComplete += BlogHttpModule_PreRenderComplete;
					}
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		void BlogHttpModule_PreRenderComplete(object sender, EventArgs e)
		{
			try
			{
				if (HttpContext.Current.Handler is Page)
				{
					var page = _HttpApplication.Context.CurrentHandler as Page;
					if (page != null)
					{
						AddHeaderHtml(page);

						if (string.IsNullOrEmpty((string)HttpContext.Current.Items["ebeRewrittenPath"])) return;

						var oUri = new Uri((string)HttpContext.Current.Items["ebeRewrittenPath"]);

						string sPath = oUri.PathAndQuery;

						HttpContext.Current.RewritePath(sPath);
					}
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		private static void AddHeaderHtml(Page page)
		{
			try
			{
				// header is null, e.g. if we click login
				if (page.Header == null) return;
				Control oHead = page.Header.FindControl("head");
				if (oHead != null)
				{
					var additionalHead = new LiteralControl();

					string webUrl = SPContext.Current.Web.Url;
					// add links for autodiscovering blogsettings for blogging software
					string rsdAutodiscoverUrl = webUrl + "/rsdAutoDiscovery.axd";
					string wlwAutodiscoverUrl = webUrl + "/wlwAutoDiscovery.axd";
					additionalHead.Text = "\r\n<link rel=\"EditURI\" type=\"application/rsd+xml\" href=\"" + rsdAutodiscoverUrl + "\" />";
					additionalHead.Text += "\r\n<link rel=\"wlwmanifest\" type=\"application/wlwmanifest+xml\" href=\"" + wlwAutodiscoverUrl + "\" />";

					try
					{
						if (!BlogContext.Current.Settings.DisableConvertClientDates)
							additionalHead.Text += "\r\n<script>var d=new Date(2020, 1, 9);document.cookie = 'CksEbeTZ' + '=' + escape(d.getTimezoneOffset()) + '; expires=' + d.toGMTString() + '; path=/';</script>";
					}
					catch { }

					additionalHead.Text += "\r\n";
					oHead.Controls.Add(additionalHead);
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
			}
		}

		void App_BeginRequest(object sender, EventArgs e)
		{
			try
			{
				// Issues with "_layouts" pages (url is root based)
				var currentSiteUri = new Uri(HttpContext.Current.Request.Url, HttpContext.Current.Request.RawUrl);
				if (!FileTypeHandled(currentSiteUri)) return;

				string url = currentSiteUri.ToString().ToLower();

				if (url.Contains("metawebblog.ashx") || url.Contains("metaweblog.ashx") || url.Contains("metaweblog.aspx"))
				{
					Trace.WriteLine("Using MetaWeblogApi for Url " + url);
					MetaWeblogHandler.HandleMetaWeblogRequest(_HttpApplication);
					return;
				}

				if (url.Contains("?")) url = url.Substring(0, url.IndexOf("?"));
				SPSecurity.RunWithElevatedPrivileges(() => RewriteContextUrl(url));
			}
			catch (ThreadAbortException)
			{
				// occures if redirected
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="url">full url e.g. http://www.hezser.de/blog/default.aspx</param>
		private static void RewriteContextUrl(string url)
		{
			using (var oSite = new SPSite(url))
			using (SPWeb oWeb = oSite.OpenWeb())
			{
				if (BlogContext.BlogEnabled(oWeb) == false) return;

				var bs = new BlogSettings(url);
				if (!bs.MtfEnabled) return;

				BlogContext blogContext = BlogContext.Current;
				HttpContext httpContext = HttpContext.Current;
				if (blogContext.UrlProvider.UrlHandled(url))
				{
					blogContext.UrlProvider.RewriteUrl(httpContext.Request.Url, httpContext, oWeb, bs);
					httpContext.Items["_ebeSPItemPath"] = httpContext.Request.Url.PathAndQuery;
				}
				else if (!url.Contains(".") || url.EndsWith("default.aspx") || url.EndsWith(bs.HomePage))
				{
					blogContext.UrlProvider.CheckRootUrl(httpContext.Request.Url, httpContext, oWeb);
					httpContext.Items["_ebeSPItemPath"] = httpContext.Request.Url.PathAndQuery;
				}
			}
		}

		void BlogHttpModule_PreInit(object sender, EventArgs e)
		{
			try
			{
				if ((HttpContext.Current.Handler is Page) == false) return;

				string themesLocation = "~site";
				var page = (Page)HttpContext.Current.Handler;

				BlogContext blogContext = BlogContext.Current;
				if (blogContext.Settings.MtfEnabled)
				{
					SPContext spContext = SPContext.Current;
					if (blogContext.Settings.UseSiteCollectionThemes && spContext.Web.IsRootWeb) themesLocation = "~sitecollection";

					string masterPageFile = page.MasterPageFile;
					if (((masterPageFile != null && masterPageFile.EndsWith("theme.master")) || blogContext.Settings.ThemeAllAspxPages))
					{
						if (masterPageFile != null && masterPageFile.ToLower().Contains("_layouts/"))
						{
							page.MasterPageFile = string.Format(spContext.Site.ServerRelativeUrl + "themes/{0}/theme.master", blogContext.BlogTheme);
						}
						else
						{
							page.MasterPageFile = string.Format("{1}/themes/{0}/theme.master", blogContext.BlogTheme, themesLocation);
						}

						// make sure the masterpage exists. if not, use the default
						var masterpage = spContext.Web.GetFile(string.Format("themes/{0}/theme.master", blogContext.BlogTheme));
						if (masterpage.Exists == false) page.MasterPageFile = string.Format("{0}/themes/default/theme.master", themesLocation);
					}
					else if (blogContext.Settings.ThemeWikiPages)
					{
						// only change the masterpage if we are in display mode
						string rootFolderUrl = SPUrlUtility.CombineUrl(spContext.Web.Url, spContext.List.RootFolder.Url);
						if (!page.Request.Url.ToString().StartsWith(SPUrlUtility.CombineUrl(rootFolderUrl, "Forms")))
						{
							CheckContentTypeThemed(themesLocation, page, "Wiki Page");
						}
					}
				}

				if (blogContext.Settings.PageOutputCacheTimeout > 0)
				{
					page.Unload += Page_Unload;
					page.Load += Page_Load;
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine("PreInit Error");
				Trace.WriteLine(ex);
			}
		}

		protected virtual void CheckContentTypeThemed(string themesLocation, Page page, string sContentType)
		{
			Debug.WriteLine(string.Format("Is content type themed: {0}  ({1})", sContentType, page.Request.Url));
			if (!(HttpContext.Current.Handler is Page) || SPContext.Current.ListItem == null) return;

			if (!BlogContext.IsRequestForContentType(sContentType) || page.MasterPageFile == null) return;

			page.MasterPageFile = string.Format("{1}/themes/{0}/theme.master", BlogContext.Current.BlogTheme, themesLocation);
			Debug.WriteLine(string.Format("Themeing...{0} with {1}", HttpContext.Current.Request.Url, themesLocation));
		}

		static void AppPostReleaseRequestState(object sender, EventArgs e)
		{
			if (HttpContext.Current.Items["_ebeSPItemPath"] == null || BlogContext.Current.Settings.PageOutputCacheTimeout <= 0) return;

			Trace.Write("Restoring: " + (string)HttpContext.Current.Items["_ebeSPItemPath"]);
			HttpContext.Current.RewritePath((string)HttpContext.Current.Items["_ebeSPItemPath"]);
		}

		static void Page_Load(object sender, EventArgs e)
		{
			BlogContext blogContext = BlogContext.Current;
			if (!blogContext.Settings.CachingEnabled || blogContext.Settings.PageOutputCacheTimeout <= 0)
			{
				// disable caching
				return;
			}

			var httpContext = HttpContext.Current;
			// if we are not anonymous and the web allows anonymous access, don't cache because admin controls would be visible to all users
			if (httpContext.Request.IsAuthenticated && SPContext.Current.Web.AnonymousState != SPWeb.WebAnonymousState.Disabled)
			{
				// don't cache this request
				return;
			}

			HttpCachePolicy cache = httpContext.Response.Cache;
			cache.SetCacheability(HttpCacheability.ServerAndPrivate);

			if (!string.IsNullOrEmpty(blogContext.Settings.PageOutputCacheVaryByParam))
			{
				string[] arParams = blogContext.Settings.PageOutputCacheVaryByParam.Split(';');
				foreach (string s in arParams)
				{
					cache.VaryByParams[s] = true;
				}
			}

			cache.SetExpires(DateTime.Now.AddMinutes(blogContext.Settings.PageOutputCacheTimeout));
			cache.SetValidUntilExpires(true);
			cache.SetLastModified(httpContext.Timestamp);
			cache.AddValidationCallback(ValidateCache, SPContext.Current.Web.LastItemModifiedDate);

			httpContext.Items["DontSetCacheHeaders"] = 1;
			httpContext.Items["DisableSettingBrowserCacheHeaders"] = "1";
			httpContext.Items["CacheHeadersSet"] = true;
			httpContext.Items["useContext"] = true;
		}

		private static void ValidateCache(HttpContext context, Object data, ref HttpValidationStatus status)
		{
			if (data == null || BlogContext.Current.IsBlogOwner || BlogContext.Current.IsBlogContributor)
			{
				// The cache is not invalidated, but the page is executed as normal rather than returned from the cache.
				status = HttpValidationStatus.IgnoreThisRequest;
				return;
			}

			var oCachedVersion = (DateTime)data;
			if (DateTime.Compare(oCachedVersion, SPContext.Current.Web.LastItemModifiedDate) < 0)
			{
				// The cache is invalidated, and the page is executed as normal.
				status = HttpValidationStatus.Invalid;
			}
			else
			{
				// The cache is validated, and the page is returned from the cache.
				status = HttpValidationStatus.Valid;
			}
		}

		static void Page_Unload(object sender, EventArgs e)
		{
			try
			{
				BlogSettings blogSettings = BlogContext.Current.Settings;
				if (blogSettings.CachingEnabled && blogSettings.PageOutputCacheTimeout > 0)
				{
					HttpContext.Current.Response.Write("<!-- Cached at " + DateTime.Now + " -->");
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		#endregion
	}
}